package cn.lijiajia3515.cairo.system.modules.dictionary;

import cn.lijiajia3515.cairo.core.CoreConstants;
import cn.lijiajia3515.cairo.core.page.Page;
import cn.lijiajia3515.cairo.mongo.data.MetaMongo;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.lijiajia3515.cairo.system.modules.SystemMongoConstants.Collection.DICTIONARY;

@Slf4j
@Service
public class DictionaryService {
	private final MongoTemplate mongoTemplate;

	DictionaryService(MongoTemplate mongoTemplate) {
		this.mongoTemplate = mongoTemplate;
	}

	/**
	 * 保存字典
	 *
	 * @param client client
	 * @param param  param
	 * @return
	 */
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	Optional<Dictionary> save(@NotNull String client, @Validated SaveDictionaryParam param) {
		DictionaryMongo dictionaryMongo = DictionaryMongo.builder()
			.client(client)
			.id(Optional.ofNullable(param.getId()).orElse(CoreConstants.SNOWFLAKE.nextIdStr()))
			.name(param.getName())
			.items(
				Optional.ofNullable(param.getItems())
					.stream()
					.flatMap(Collection::stream)
					.map(x ->
						DictionaryMongo.Item.builder()
							.id(CoreConstants.SNOWFLAKE.nextIdStr())
							.value(Optional.ofNullable(x.getValue()).orElse(CoreConstants.SNOWFLAKE.nextIdStr()))
							.name(x.getName())
							.meta(new MetaMongo().setSort(CoreConstants.SNOWFLAKE.nextId()))
							.build()
					)
					.collect(Collectors.toList())
			)
			.build();
		dictionaryMongo = mongoTemplate.insert(dictionaryMongo, DICTIONARY);
		log.debug("[insert] result-> {}", dictionaryMongo);

		return find(client, param.getId());
	}

	/**
	 * 修改字典
	 *
	 * @param param param
	 * @return 1
	 */
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	Optional<Dictionary> modify(@NotNull String client, @Validated DictionaryModifyParam param) {
		final List<DictionaryMongo.Item> items = Optional.ofNullable(param.getItems())
			.stream()
			.flatMap(Collection::stream)
			.map(x ->
				DictionaryMongo.Item.builder()
					.id(Optional.ofNullable(x.getId()).orElse(CoreConstants.SNOWFLAKE.nextIdStr()))
					.value(Optional.ofNullable(x.getValue()).orElse(CoreConstants.SNOWFLAKE.nextIdStr()))
					.name(x.getName())
					.meta(new MetaMongo().setSort(CoreConstants.SNOWFLAKE.nextId()))
					.build()
			)
			.collect(Collectors.toList());

		Query query = Query.query(
			Criteria.where(DictionaryMongo.FIELD.CLIENT).is(client)
				.and(DictionaryMongo.FIELD.ID).is(param.getId())
		);

		Update update = Update.update(DictionaryMongo.FIELD.Name, param.getName());
		Optional.of(items).filter(x -> !x.isEmpty()).ifPresent(x -> update.set(DictionaryMongo.FIELD.ITEMS.SELF, x));

		final UpdateResult updateResult = mongoTemplate.updateFirst(query, update, DictionaryMongo.class, DICTIONARY);

		log.debug("[modify] result-> {}", updateResult);
		return find(client, param.getId());

	}

	/**
	 * 删除字典
	 *
	 * @param client client
	 * @param param  param
	 * @return remove dictionary list
	 */
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	List<Dictionary> delete(@NotNull String client, @Validated DeleteDictionaryParam param) {
		Criteria criteria = Criteria
			.where(DictionaryMongo.FIELD.CLIENT).is(client)
			.and(DictionaryMongo.FIELD.ID).in(param.getIds());

		Query query = Query.query(criteria);
		List<DictionaryMongo> ms = mongoTemplate.findAllAndRemove(query, DictionaryMongo.class, DICTIONARY);
		return ms.stream().filter(Objects::nonNull).map(DictionaryConverter::convert).collect(Collectors.toList());
	}

	/**
	 * 插入字典项
	 *
	 * @param param param
	 * @return dictionary optional
	 */
	Optional<Dictionary> putItems(@NotNull String client, @Validated PutDictionaryItemParam param) {
		final Criteria criteria = Criteria
			.where(DictionaryMongo.FIELD.CLIENT).is(client)
			.and(DictionaryMongo.FIELD.ID).is(param.getId());

		final List<DictionaryMongo.Item> newItems = Optional
			.ofNullable(param.getItems())
			.stream()
			.flatMap(Collection::stream)
			.map(x -> DictionaryMongo.Item.builder()
				.id(Optional.ofNullable(x.getId()).orElse(CoreConstants.SNOWFLAKE.nextIdStr()))
				.value(x.getValue())
				.name(x.getName())
				.meta(new MetaMongo().setSort(CoreConstants.SNOWFLAKE.nextId()))
				.build())
			.collect(Collectors.toList());

		Query query = Query.query(criteria);

		Update update = new Update().addToSet(DictionaryMongo.FIELD.ITEMS.SELF).each(newItems);

		final UpdateResult updateResult = mongoTemplate.updateFirst(query, update, DictionaryMongo.class, DICTIONARY);
		log.debug("[dictionary][putItem] result-> : {}", updateResult);

		return find(client, param.getId());
	}

	/**
	 * 修改字典项
	 *
	 * @param client client
	 * @param param  param param
	 * @return 修改后的字典
	 */
	Optional<Dictionary> modifyItem(@NotNull String client, @Validated ModifyDictionaryItemParam param) {
		param.getItems()
			.forEach(item -> {
				final Criteria criteria = Criteria
					.where(DictionaryMongo.FIELD.CLIENT).is(client)
					.and(DictionaryMongo.FIELD.ID).is(param.getId())
					.and(DictionaryMongo.FIELD.ITEMS.ID).is(item.getId());
				final Query query = Query.query(criteria);

				final Update update = new Update()
					.set(DictionaryMongo.FIELD.ITEMS.$VALUE, item.getValue())
					.set(DictionaryMongo.FIELD.ITEMS.$NAME, item.getName());

				final UpdateResult updateResult = mongoTemplate.updateFirst(query, update, DictionaryMongo.class, DICTIONARY);
				log.debug("[dictionary][putItem] result-> : {}", updateResult);
			});
		return find(client, param.getId());
	}

	/**
	 * 删除字典项
	 *
	 * @param client
	 * @param param  param
	 * @return 已删除的字典
	 */
	Optional<Dictionary> deleteItems(@NotNull String client, @Validated DeleteDictionaryItemParam param) {
		final Criteria criteria = Criteria
			.where(DictionaryMongo.FIELD.CLIENT).is(client)
			.and(DictionaryMongo.FIELD.ID).is(param.getId());

		final Query query = Query.query(criteria);
		Object[] items = mongoTemplate.find(query, DictionaryMongo.class, DICTIONARY).stream().flatMap(x -> Optional.ofNullable(x.getItems()).stream())
			.flatMap(Collection::stream)
			.filter(x -> param.getItemIds().contains(x.getId()))
			.toArray();
		final Update update = new Update();
		update.pullAll(DictionaryMongo.FIELD.ITEMS.SELF, items);

		final UpdateResult updateResult = mongoTemplate.updateFirst(query, update, DictionaryMongo.class, DICTIONARY);
		log.debug("[dictionary][deleteItem] result-> : {}", updateResult);

		return find(client, param.getId());
	}

	/**
	 * 字典查询
	 *
	 * @param client client
	 * @param param  查询参数
	 * @return x
	 */
	List<Dictionary> find(@NotNull String client, @Validated FindDictionaryParam param) {

		Criteria criteria = buildCriteria(client, param);
		Query query = Query.query(criteria).with(defaultSort());
		return mongoTemplate.find(query, DictionaryMongo.class, DICTIONARY).stream()
			.filter(Objects::nonNull)
			.map(DictionaryConverter::convert)
			.peek(filterParam(param))
			.collect(Collectors.toList());
	}

	/**
	 * 分页查询参数
	 *
	 * @param client client
	 * @param param  查询参数
	 * @return x
	 */
	Page<Dictionary> findPage(@NotNull String client, @Validated FindDictionaryParam param) {
		Criteria criteria = buildCriteria(client, param);

		Query query = Query.query(criteria);
		long total = mongoTemplate.count(query, DictionaryMongo.class, DICTIONARY);

		query.with(param.pageable()).with(defaultSort());
		List<Dictionary> contents = mongoTemplate.find(query, DictionaryMongo.class, DICTIONARY)
			.stream()
			.filter(Objects::nonNull)
			.map(DictionaryConverter::convert)
			.peek(filterParam(param))
			.collect(Collectors.toList());
		return new Page<>(param, contents, total);
	}

	/**
	 * 根据 id 查询 字典
	 *
	 * @param client client
	 * @param id     id
	 * @return 字典
	 */
	Optional<Dictionary> find(@NotNull String client, @NotNull String id) {
		Criteria criteria = Criteria.where(DictionaryMongo.FIELD.CLIENT).is(client).and(DictionaryMongo.FIELD.ID).is(id);
		Query query = Query.query(criteria);
		return Optional.ofNullable(mongoTemplate.findOne(query, DictionaryMongo.class, DICTIONARY))
			.map(DictionaryConverter::convert);
	}


	/**
	 * 构建 查询条件
	 *
	 * @param client client
	 * @param param  param
	 * @return 查询条件
	 */
	Criteria buildCriteria(@NotNull String client, FindDictionaryParam param) {
		Criteria criteria = Criteria.where(DictionaryMongo.FIELD.CLIENT).is(client);
		Stream<Criteria> keywordCriteria = Optional.ofNullable(param.getKeyword()).map(y -> Criteria.where(DictionaryMongo.FIELD.Name).regex(y)).stream();
		Stream<Criteria> itemsCriteria = Optional.ofNullable(param.getItems())
			.orElse(Collections.emptySet())
			.stream()
			.filter(Objects::nonNull)
			.filter(x -> x.getId() != null)
			.map(i -> {
					Criteria iCriteria = Criteria.where(DictionaryMongo.FIELD.ID).is(i.getId());
					Optional.ofNullable(i.getItemIds()).filter(x -> !x.isEmpty()).ifPresent(itemIds -> iCriteria.and(DictionaryMongo.FIELD.ITEMS.ID).in(itemIds));
					Optional.ofNullable(i.getItemValues()).filter(x -> !x.isEmpty()).ifPresent(itemValues -> iCriteria.and(DictionaryMongo.FIELD.ITEMS.VALUE).in(itemValues));
					return iCriteria;
				}
			);
		Optional.of(Stream.concat(keywordCriteria, itemsCriteria)
				.filter(Objects::nonNull)
				.toArray(Criteria[]::new))
			.filter(x -> x.length > 0)
			.ifPresent(criteria::orOperator);

		return criteria;
	}

	Sort defaultSort() {
		return Sort.by(
			Sort.Order.asc(DictionaryMongo.FIELD.META.SORT),
			Sort.Order.asc(DictionaryMongo.FIELD.META.CREATED.AT),
			Sort.Order.asc(DictionaryMongo.FIELD._ID),
			Sort.Order.asc(DictionaryMongo.FIELD.ITEMS.META.SORT),
			Sort.Order.asc(DictionaryMongo.FIELD.ITEMS.VALUE),
			Sort.Order.asc(DictionaryMongo.FIELD.ITEMS.ID)
		);
	}

	Consumer<Dictionary> filterParam(FindDictionaryParam param) {
		Map<String, FindDictionaryParam.Dictionary> dictionaryMap = Optional.ofNullable(param.getItems()).orElse(Collections.emptySet()).stream().collect(Collectors.toMap(FindDictionaryParam.Dictionary::getId, x -> x));
		return x -> {
			if (dictionaryMap.containsKey(x.getId())) {
				FindDictionaryParam.Dictionary dictionary = dictionaryMap.get(x.getId());
				Set<Object> itemIds = Optional.ofNullable(dictionary.getItemIds()).orElse(Collections.emptySet());
				Set<Object> itemValues = Optional.ofNullable(dictionary.getItemValues()).orElse(Collections.emptySet());
				x.setItems(x.getItems().stream()
					.filter(y -> itemIds.isEmpty() || itemIds.contains(y.getId()))
					.filter(y -> itemValues.isEmpty() || itemValues.contains(y.getValue()))
					.collect(Collectors.toList()));
			}
		};
	}

}
